/*
 * RenderWindow.cpp
 *
 *  Created on: 2009/01/05
 *      Author: Yongshik Cho
 */

#include "RenderWindow.h"
#include "Exception.h"
#include "LoggerableManager.h"
#include "RenderDevice.h"


const String RenderWindow::_RenderWindowClassName 	= _T("ClassRenderWindow");
const String RenderWindow::_DEFAULT_WINDOW_NAME 	= _T("Alto2D Engine");

template<> RenderWindow* ISingleton<RenderWindow>::m_pInstance = 0;


RenderWindow::RenderWindow(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
		int32 nCmdShow, HWND hwnd, String title, int8 centerType,
		int32 x, int32 y, int32 width, int32 height,
		bool fullscreen, bool resizable)
{
	// TODO Auto-generated constructor stub
	mInstance = hInstance;
	mPrevInstance = hPrevInstance;
	mCmdLine = lpCmdLine;
	mCmdShow = nCmdShow;
	mWnd = 0;
	mDeviceContext = 0;

	checkDesktop();

	//retrieve log output stream
	Loggerable* log = LoggerableManager::GetInstance().get();

	if(!hwnd)
	{
		mPosX = x;
		mPosY = y;
		mWidth = width;
		mHeight = height;
		mCenterType = centerType;
		mTitle = title;
		mFullscreen = fullscreen;
		mResizable = resizable;

		create();

		if(log)
		{
#ifdef UNICODE
			wofstream& out = log->getStream();
#else
			ofstream& out = log->getStream();
#endif

			out << std::endl;
			AltoLog(_T("Window has been created."));
			out << _T("pos x : ") << mPosX << std::endl;
			out << _T("pos y : ") << mPosY << std::endl;
			out << _T("width : ") << mWidth << std::endl;
			out << _T("height : ") << mHeight << std::endl;
			out << _T("center type : ") << mCenterType << std::endl;
			out << _T("title : ") << mTitle << std::endl;
			out << _T("fullscreen : ") << mFullscreen << std::endl;
			out << _T("resizable : ") << mResizable << std::endl;
		}
	}
	else
	{
		mWnd = hwnd;

		retrieve();
	}


	//logging desktop capabilities
	if(log)
	{
#ifdef UNICODE
		wofstream& out = log->getStream();
#else
		ofstream& out = log->getStream();
#endif

		out << std::endl;
		AltoLog(_T("Window Capabilities"));
		out << _T("horzsize : ") << mCaps.horzsize << std::endl;
		out << _T("vertsize : ") << mCaps.vertsize << std::endl;
		out << _T("horzres : ") << mCaps.horzres << std::endl;
		out << _T("vertres : ") << mCaps.vertres << std::endl;
		out << _T("logpixelsx : ") << mCaps.logpixelsx << std::endl;
		out << _T("logpixelsy : ") << mCaps.logpixelsy << std::endl;
		out << _T("bitspixel : ") << mCaps.bitspixel << std::endl;
		out << _T("planes : ") << mCaps.planes << std::endl;
		out << _T("physicalwidth : ") << mCaps.physicalwidth << std::endl;
		out << _T("physicalheight : ") << mCaps.physicalheight << std::endl;
		out << _T("physicaloffsetx : ") << mCaps.physicaloffsetx << std::endl;
		out << _T("physicaloffsety : ") << mCaps.physicaloffsety << std::endl;
		out << _T("vrefresh : ") << mCaps.vrefresh << std::endl;
	}
}

RenderWindow::~RenderWindow()
{
	// TODO Auto-generated destructor stub
	if(!mWnd)
		return;

	if(mDeviceContext)
	{
		::ReleaseDC(mWnd, mDeviceContext);
		mDeviceContext = 0;
	}
}

void RenderWindow::retrieve()
{
	RECT rect;
	::GetClientRect(mWnd, &rect);

	mPosX = rect.left;
	mPosY = rect.top;
	mWidth = rect.right - rect.left;
	mHeight = rect.bottom - rect.top;
	mCenterType = Rect2D::CENTER_NONE;

	uint32 textSize = ::GetWindowTextLength(mWnd);
	TCHAR* text = new TCHAR [textSize];
	::GetWindowText(mWnd, text, textSize);
	mTitle = text;
	SAFE_DELETE_ARRAY(text);

	mFullscreen = false;
	mResizable = true;

	mCaps = getDeviceCaps(mDeviceContext);
}

void RenderWindow::checkDesktop()
{
	mDesktopWnd = ::GetDesktopWindow();
	if(!mDesktopWnd)
		AltoThrow(Exception::ERR_RUNTIME);

	//retrieves desktop capabilities
	HDC hdc = ::GetDC(mDesktopWnd);
	if(!hdc)
		return;

	mDesktopCaps = getDeviceCaps(hdc);

	//retrieve log output stream
	Loggerable* log = LoggerableManager::GetInstance().get();

	if(log)
	{
#ifdef UNICODE
		wofstream& out = log->getStream();
#else
		ofstream& out = log->getStream();
#endif

		//logging desktop capabilities
		out << std::endl;
		AltoLog(_T("Desktop Capabilities"));
		out << _T("horzsize : ") << mDesktopCaps.horzsize << std::endl;
		out << _T("vertsize : ") << mDesktopCaps.vertsize << std::endl;
		out << _T("horzres : ") << mDesktopCaps.horzres << std::endl;
		out << _T("vertres : ") << mDesktopCaps.vertres << std::endl;
		out << _T("logpixelsx : ") << mDesktopCaps.logpixelsx << std::endl;
		out << _T("logpixelsy : ") << mDesktopCaps.logpixelsy << std::endl;
		out << _T("bitspixel : ") << mDesktopCaps.bitspixel << std::endl;
		out << _T("planes : ") << mDesktopCaps.planes << std::endl;
		out << _T("physicalwidth : ") << mDesktopCaps.physicalwidth << std::endl;
		out << _T("physicalheight : ") << mDesktopCaps.physicalheight << std::endl;
		out << _T("physicaloffsetx : ") << mDesktopCaps.physicaloffsetx << std::endl;
		out << _T("physicaloffsety : ") << mDesktopCaps.physicaloffsety << std::endl;
		out << _T("vrefresh : ") << mDesktopCaps.vrefresh << std::endl;
	}
}

void RenderWindow::create()
{
	WNDCLASS wc;
	memset(&wc, 0, sizeof(wc));

	//define window class
	wc.style = CS_DBLCLKS;
	wc.lpfnWndProc = (WNDPROC)RenderWindow::WindowMessageProcedure;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = mInstance;
	wc.hIcon = 0;
	wc.hCursor = 0;
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.lpszMenuName = 0;
	wc.lpszClassName = _RenderWindowClassName.c_str();

	//register window class
	RegisterClass(&wc);

	uint32 dwExStyle = 0;
	uint32 dwStyle = 0;
	if(mFullscreen)
	{
		dwStyle = WS_POPUP;
	}
	else if(mResizable)
	{
		dwStyle = WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
	}
	else
	{
		dwStyle = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX;
	}

	int32 x, y;
	makeWindowPosition(x, y);

	mWnd = ::CreateWindowEx( dwExStyle,
			_RenderWindowClassName.c_str(),
			mTitle.c_str(),
			dwStyle,
			x,
			y,
			mWidth,
			mHeight,
			0,
			0,
			mInstance,
			0);

	if(!mWnd)
		AltoThrow(Exception::ERR_MEMORY);

	//retrieve device caps from window which has been made
	mDeviceContext = ::GetDC(mWnd);
	if(!mDeviceContext)
		AltoThrow(Exception::ERR_MEMORY);

	//retrieve current device context
	mCaps = getDeviceCaps(mDeviceContext);

	if(mFullscreen)
	{
		::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE);
	}

	::ShowWindow(mWnd, mCmdShow);
	::UpdateWindow(mWnd);
}

void RenderWindow::makeWindowPosition(int32& x, int32& y)
{
	if(mFullscreen)
	{
		x = 0, y = 0;
		return;
	}

	switch(mCenterType)
	{
	case Rect2D::CENTER_NONE:
		x = mPosX, y = mPosY;
		break;
	case Rect2D::CENTER_LT:
		x = 0, y = 0;
		break;
	case Rect2D::CENTER_CT:
		x = (mDesktopCaps.horzres - mWidth) >> 1;
		y = 0;
		break;
	case Rect2D::CENTER_RT:
		x = mDesktopCaps.horzres - mWidth;
		y = 0;
		break;
	case Rect2D::CENTER_LM:
		x = 0;
		y = (mDesktopCaps.vertres - mHeight) >> 1;
		break;
	case Rect2D::CENTER_CM:
		x = (mDesktopCaps.horzres - mWidth) >> 1;
		y = (mDesktopCaps.vertres - mHeight) >> 1;
		break;
	case Rect2D::CENTER_RM:
		x = mDesktopCaps.horzres - mWidth;
		y = (mDesktopCaps.vertres - mHeight) >> 1;
		break;
	case Rect2D::CENTER_LB:
		x = 0;
		y = mDesktopCaps.vertres - mHeight;
		break;
	case Rect2D::CENTER_CB:
		x = (mDesktopCaps.horzres - mWidth) >> 1;
		y = mDesktopCaps.vertres - mHeight;
		break;
	case Rect2D::CENTER_RB:
		x = mDesktopCaps.horzres - mWidth;
		y = mDesktopCaps.vertres - mHeight;
		break;
	}
}

LRESULT WINAPI RenderWindow::WindowMessageProcedure(HWND hWnd, uint32 msg, WPARAM wParam, LPARAM lParam)
{
	RenderWindow& renderWindow = RenderWindow::GetInstance();

	switch(msg)
	{
	case WM_DESTROY:
		::PostQuitMessage(0);
		break;

	case WM_MOVE:
		{
			RECT rect;
			::GetWindowRect(hWnd, &rect);
			renderWindow.setPosX(rect.left);
			renderWindow.setPosY(rect.top);
			renderWindow.setWidth(rect.right - rect.left);
			renderWindow.setHeight(rect.bottom - rect.top);
		}
		break;

	case WM_MOVING:
		break;

	case WM_SIZE:
		{
			RenderDevice* device = RenderDevice::GetInstancePtr();
			if(!device)
				break;

			uint8 type;
			switch(wParam)
			{
			case SIZE_RESTORED:
				type = RenderWindow::_SIZE_RESTORED;
				break;
			case SIZE_MINIMIZED:
				type = RenderWindow::_SIZE_MINIMIZED;
				break;
			case SIZE_MAXIMIZED:
				type = RenderWindow::_SIZE_MAXIMIZED;
				break;
			case SIZE_MAXSHOW:
				type = RenderWindow::_SIZE_MAXSHOW;
				break;
			case SIZE_MAXHIDE:
				type = RenderWindow::_SIZE_MAXHIDE;
				break;
			}

			uint16 width = LOWORD(lParam);
			uint16 height = HIWORD(lParam);
			device->onSize(type, Vector2D(width, height));
		}
		break;

	default:
		return ::DefWindowProc(hWnd, msg, wParam, lParam);
	}

	return 0L;
}

void RenderWindow::run()
{
}

const Rect2D RenderWindow::getClientRect()
{
	if(!mWnd)
		return Rect2D(0.f, 0.f);

	RECT rect;
	::GetClientRect(mWnd, &rect);

	return Rect2D((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
}

void RenderWindow::setPosition(const int32 x, const int32 y)
{
	mPosX = x;
	mPosY = y;

	::SetWindowPos(mWnd, 0, mPosX, mPosY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

void RenderWindow::setSize(const int32 sx, const int32 sy)
{
	::SetWindowPos(mWnd, 0, 0, 0, sx, sy, SWP_NOREPOSITION | SWP_NOZORDER);
}

void RenderWindow::setZorder(const HWND& hWndInsertAfter)
{
	::SetWindowPos(mWnd, hWndInsertAfter, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

const DeviceCaps RenderWindow::getDeviceCaps(const HDC& hdc)
{
	DeviceCaps caps;

	caps.horzsize = ::GetDeviceCaps(hdc, HORZSIZE);
	caps.vertsize = ::GetDeviceCaps(hdc, VERTSIZE);
	caps.horzres = ::GetDeviceCaps(hdc, HORZRES);
	caps.vertres = ::GetDeviceCaps(hdc, VERTRES);
	caps.logpixelsx = ::GetDeviceCaps(hdc, LOGPIXELSX);
	caps.logpixelsy = ::GetDeviceCaps(hdc, LOGPIXELSY);
	caps.bitspixel = ::GetDeviceCaps(hdc, BITSPIXEL);
	caps.planes = ::GetDeviceCaps(hdc, PLANES);
	caps.physicalwidth = ::GetDeviceCaps(hdc, PHYSICALWIDTH);
	caps.physicalheight = ::GetDeviceCaps(hdc, PHYSICALHEIGHT);
	caps.physicaloffsetx = ::GetDeviceCaps(hdc, PHYSICALOFFSETX);
	caps.physicaloffsety = ::GetDeviceCaps(hdc, PHYSICALOFFSETY);
	caps.vrefresh = ::GetDeviceCaps(hdc, VREFRESH);

	return caps;
}
